namespace WinRT.Triggers
{
    #region Using directives

    using System;

    using Windows.UI.Xaml;

    #endregion

    /// <summary>
    /// A <see cref="Trigger"/> much like <see cref="PropertyChangedTrigger"/>, except this implementation
    /// only fires when the property is set to a specific value.
    /// </summary>
    public class PropertySetTrigger : BoundTrigger
    {
        #region Static Fields

        /// <summary>
        /// Defines the RequiredValue dependency property, of type <see cref="string"/>.
        /// </summary>
        public static readonly DependencyProperty RequiredValueProperty = DependencyProperty.Register(
            "RequiredValue",
            typeof(object),
            typeof(PropertySetTrigger),
            new PropertyMetadata(DependencyProperty.UnsetValue));

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the expected value that must be met for the state change to occur.
        /// </summary>
        public object RequiredValue
        {
            get
            {
                return this.GetValue(RequiredValueProperty);
            }

            set
            {
                this.SetValue(RequiredValueProperty, value);
            }
        }

        #endregion

        #region Methods

        /// <inheritdoc />
        protected override void OnBindingValueChanged(object oldValue, object newValue)
        {
            var isEqual = false;

            // Handle null cases, accept empty string to get a match
            if (newValue == null)
            {
                isEqual = this.RequiredValue == null
                          || (this.RequiredValue is string && string.IsNullOrEmpty((string)this.RequiredValue));
            }
            else
            {
                // First, use Equals to determine match
                isEqual = newValue.Equals(this.RequiredValue);

                // Try some fall back, by matching string
                if (!isEqual && this.RequiredValue != null)
                {
                    // Make some special allowances for numeric type, to ensure our values specified as 
                    // string in xaml do not break, if the app runs in a different culture (i.e. one that switches ',' for '.')
                    if (newValue is float || newValue is double)
                    {
                        double value;
                        if (double.TryParse(
                            this.RequiredValue.ToString(),
                            System.Globalization.NumberStyles.Any,
                            System.Globalization.CultureInfo.InvariantCulture, 
                            out value))
                        {
                            // Because we're operating on a double, allow for loss of precision errors
                            // when converting back from a string
                            isEqual = Math.Abs(value - (double)newValue) < double.Epsilon;
                        }
                    }
                    else
                    {
                        // Brute force approach - convert both values to string and compare
                        isEqual = string.Equals(newValue.ToString(), this.RequiredValue.ToString(), StringComparison.OrdinalIgnoreCase);
                    }
                }
            }

            if (isEqual)
            {
                this.OnTriggered();
            }
        }

        #endregion
    }
}